//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// J2ME-Lib source file
// Copyright (c) 2007 Elmar Sonnenschein / esoco GmbH
// Last Change: 27.06.2007 by eso
//
// J2ME-Lib is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 2.1 of the License, or (at your option)
// any later version.
//
// J2ME-Lib is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with J2ME-Lib; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA or use the contact
// information from the GNU website http://www.gnu.org
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
package de.esoco.j2me.resource;

import java.io.IOException;

import javax.microedition.lcdui.Image;


/********************************************************************
 * Base class for resources (strings, images etc.). Modelled after the JDK
 * java.util.ResourceBundle class, but simplified to respect the constrained
 * environment of J2ME devices.
 *
 * @author eso
 */
public abstract class ResourceBundle
{
	//~ Static fields/initializers ---------------------------------------------

	private static ResourceBundle aCurrentBundle;

	//~ Instance fields --------------------------------------------------------

	private ResourceBundle rParent;

	//~ Constructors -----------------------------------------------------------

	/***************************************
	 * Empty Constructor for subclassing.
	 */
	public ResourceBundle()
	{
	}

	//~ Methods ----------------------------------------------------------------

	/***************************************
	 * Returns the current resource bundle instance. This will be the last
	 * resource bundle object that has been created by a call to the method
	 * {@link #loadBundle(String, boolean)}. Previously loaded bundles will be
	 * registered as the parents of the last loaded bundle.
	 *
	 * @return The current resource bundle
	 */
	public static ResourceBundle getCurrent()
	{
		return aCurrentBundle;
	}

	/***************************************
	 * Invokes {@link #loadBundle(String, boolean)} with the boolean parameter
	 * set to TRUE. Normally this method should be used to load resource bundles
	 * unless it is absolutely necessary to use a resource bundle outside of the
	 * current bundle chain.
	 *
	 * @see #loadBundle(String, boolean)
	 */
	public static ResourceBundle loadBundle(String sResource)
	{
		return loadBundle(sResource, true);
	}

	/***************************************
	 * Invokes {@link #loadBundle(String, String, boolean)} with the device
	 * locale. The locale will be determined by invoking the method {@link
	 * System#getProperty(String)} with the key "microedition.locale".
	 *
	 * @see #loadBundle(String, String, boolean)
	 */
	public static ResourceBundle loadBundle(String  sResource,
											boolean bSetCurrent)
	{
		return loadBundle(sResource, getLocale(), bSetCurrent);
	}

	/***************************************
	 * Loads a resource bundle instance for a certain base name and locale. This
	 * method works similar to the J2SE ResourceBundle.getBundle() methods. From
	 * the base and locale names it creates a bundle candidate name in the form
	 * BaseName_Locale (examples: MyBundle, MyBundle_de, MyBundle_fr_CH). It
	 * then iterates backwards from the specific to the more generic bundles
	 * (MyBundle_de_DE -&gt; MyBundle_de -&gt; MyBundle).
	 *
	 * <p>For each bundle candidate name this implementation tries to load a
	 * corresponding bundle. First it tries to find a class with the candidate
	 * name (e.g. res.MyBundle_de). If no such class can be found the name is
	 * converted to a resource properties file name by replacing dots with
	 * slashes (/res/MyBundle_de.properties). Then it is tried to load that
	 * properties file and to create a {@link PropertyResourceBundle} from it.
	 * This is continued until it has been tried with the base name (i.e.
	 * without any locale appended). If no bundle that is at least matching the
	 * base name could be found an IllegalArgumentException will be thrown.</p>
	 *
	 * <p>If in the loop described above multiple bundles are found the more
	 * generic bundles will become parent bundles of any previously loaded
	 * bundle. By this specific bundles inherit the keys from the general
	 * bundles and therefore don't need to redefine all resource keys.</p>
	 *
	 * @param  sBaseName   The base name of the resource bundle to load
	 * @param  sLocale     The locale to be used to load a bundle (may be NULL)
	 * @param  bSetCurrent If TRUE {@link #setCurrent(ResourceBundle)} will be
	 *                     invoked for the loaded bundle
	 *
	 * @return The ResourceBundle instance for the given base name and locale
	 *
	 * @throws IllegalArgumentException If the given resource bundle does not
	 *                                  exist or if it cannot be created
	 */
	public static ResourceBundle loadBundle(String  sBaseName,
											String  sLocale,
											boolean bSetCurrent)
	{
		sLocale = (sLocale != null ? sLocale.replace('-', '_') : "");

		String		   sClassCandidate = sBaseName + "_" + sLocale;
		String		   sFileCandidate  = sClassCandidate.replace('.', '/');
//		Class		   rClass		   = sBaseName.getClass();
		ResourceBundle aNewBundle	   = null;
		int			   nNextLocale;

		do
		{
			ResourceBundle aBundle = null;

			try
			{
				Class c = Class.forName(sClassCandidate);

				aBundle = (ResourceBundle) c.newInstance();
			}
//			catch (ClassNotFoundException e)
//			{
//				InputStream is = rClass.getResourceAsStream("/" +
//															sFileCandidate +
//															".properties");
//
//				if (is != null)
//				{
//					try
//					{
//						aBundle = new PropertyResourceBundle(is);
//					}
//					catch (IOException eIO)
//					{
//						throw new IllegalArgumentException("Error reading properties from " +
//														   sFileCandidate +
//														   ": " + eIO);
//					}
//				}
//			}
			catch (ClassCastException eCC)
			{
				throw new IllegalArgumentException("Not a ResourceBundle subclass: " +
												   sClassCandidate);
			}
			catch (Exception e)
			{
				throw new IllegalArgumentException("Error creating class: " +
												   sClassCandidate);
			}

			if (aBundle != null)
			{
				if (aNewBundle == null)
				{
					aNewBundle = aBundle;
				}
				else
				{
					aNewBundle.setParent(aBundle);
				}
			}
			nNextLocale = sClassCandidate.lastIndexOf('_');

			if (nNextLocale > 0)
			{
				sClassCandidate = sClassCandidate.substring(0, nNextLocale);
				sFileCandidate  = sFileCandidate.substring(0, nNextLocale);
			}
		}
		while (nNextLocale > 0);

		if (aNewBundle != null)
		{
			if (bSetCurrent)
			{
				setCurrent(aNewBundle);
			}
		}
		else
		{
			throw new IllegalArgumentException("No matching resource found: " +
											   sBaseName);
		}

		return aNewBundle;
	}

	/***************************************
	 * Sets the current resource bundle. If a current bundle already exists it
	 * will become the parent of the new bundle. That means that resources from
	 * last loaded bundles will be returned with precedence. If a resource key
	 * is not found there the hierarchy of parent resources will be searched in
	 * the reverse order they have been loaded. This can be used by applications
	 * to override library resources by having the same resource key with a
	 * different value in their own resource. The application just has to invoke
	 * loadBundle() for it's own resource after the library resource has been
	 * loaded. Furthermore it allows an application to access library-specific
	 * resources if it knows the corresponding key.
	 *
	 * <p>To prevent inadvertent overriding of resource keys in the parent chain
	 * it is strongly recommended to use a certain prefix for the keys of
	 * library resources. In any case library developers should always
	 * thoroughly document resource handling, initialization and prefixes so
	 * that application developers can adapt their code accordingly.</p>
	 *
	 * @param rNewCurrent The new current bundle
	 */
	public static void setCurrent(ResourceBundle rNewCurrent)
	{
		if (aCurrentBundle != null)
		{
			rNewCurrent.setParent(aCurrentBundle);
		}

		aCurrentBundle = rNewCurrent;
	}

	/***************************************
	 * Returns an Image object from the resource. The image is created with a
	 * call to {@link Image#createImage(String)} with the resource string
	 * returned for the key. The image is not cached inside the resource, so the
	 * caller should keep the reference if he needs to use the image multiple
	 * times.
	 *
	 * @param  sKey The key value
	 *
	 * @return The corresponding Image object or NULL if no matching String
	 *         object with the image name exists in the ResourceBundle or if the
	 *         call to createImage() resulted in an IOException (i.e. an image
	 *         with the given name could not be found)
	 */
	public final Image getImage(String sKey)
	{
		String sName = getString(sKey);

		try
		{
			if (sName != null)
			{
				return Image.createImage(sName);
			}
			else
			{
				return null;
			}
		}
		catch (IOException eIO)
		{
			// return null if image doesn't exist
			return null;
		}
	}

	/***************************************
	 * Returns a resource object for a particular key from this bundle or it's
	 * parent if one exists (by recursively invoking getObject()).
	 *
	 * @param  sKey The key value
	 *
	 * @return The corresponding resource object or NULL if no matching object
	 *         exists in the ResourceBundle
	 */
	public final Object getObject(String sKey)
	{
		Object rObj = handleGetObject(sKey);

		if (rObj == null && rParent != null)
		{
			rObj = rParent.getObject(sKey);
		}

		return rObj;
	}

	/***************************************
	 * Returns a String object from the resource. Attention: if the object is
	 * not actually a String instance, a ClassCastException will be thrown.
	 *
	 * @param  sKey The key value
	 *
	 * @return The corresponding string or NULL if no matching object exists in
	 *         the ResourceBundle
	 */
	public final String getString(String sKey)
	{
		return (String) getObject(sKey);
	}

	/***************************************
	 * Must be implemented by subclasses to return a resource object for a
	 * particular key.
	 *
	 * @param  key The key value
	 *
	 * @return The corresponding resource object or NULL if no matching object
	 *         exists in the ResourceBundle
	 */
	protected abstract Object handleGetObject(String key);

	/***************************************
	 * Recursively sets the parent resource bundle of this instance. The given
	 * bundle will become the parent of the last parent in the chain which
	 * doesn't have a parent already.
	 *
	 * @param rBundle The ResourceBundle instance to be used as the parent
	 */
	protected void setParent(ResourceBundle rBundle)
	{
		if (rParent != null)
		{
			rParent.setParent(rBundle);
		}
		else
		{
			rParent = rBundle;
		}
	}

	/***************************************
	 * Tries to determine the current locale from the system properties.
	 *
	 * @return The locale string or NULL if it could not be determined
	 */
	private static String getLocale()
	{
		try
		{
			return System.getProperty("microedition.locale");
		}
		catch (Exception e)
		{
			return null;
		}
	}
	
	public abstract String getBundleLocale();
}
